//信令状态回调
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:app/core/rtc/base/base_signaling.dart';
import 'package:app/core/rtc/base/call_state.dart';
import 'package:app/core/rtc/base/session_type.dart';
import 'package:app/core/rtc/base/signaling_call_back.dart';
import 'package:app/core/rtc/base/signaling_state.dart';
import 'package:app/core/rtc/config/constraints.dart';
import 'package:app/core/rtc/impl/rtc_ice_server.dart';
import 'package:app/core/rtc/impl/rtc_socket.dart';
import 'package:app/core/rtc/impl/session.dart';
import 'package:app/core/utils/helpers/device_util.dart';
import 'package:app/core/utils/helpers/log_print.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sprintf/sprintf.dart';

import '../base/call_state.dart';
import '../base/session_type.dart';
import 'session.dart';

class RtcSignaling extends BaseSignaling {
  String _host;
  int _p2pPort = 8000;
  String _api;
  SignalingCallback signalingCallback;

  RtcSignaling(this._host, this._p2pPort, this._api);

  int _turnPort = 9000;

  RtcSocket _socket;
  RtcIceServer _rtcIceServer;
  MediaStream _localStream;
  MediaRecorder _mediaRecorder;

  JsonEncoder _encoder = JsonEncoder();
  JsonDecoder _decoder = JsonDecoder();

  List<MediaStream> _remoteStreams = <MediaStream>[];
  Map<String, Session> _sessions = {};

  String userPhone;
  String userName;
  String userAgent;

  setSignalingCallback(SignalingCallback callback) {
    this.signalingCallback = callback;
  }

  @override
  connect(_userName, _userPhone, token) async {
    this.userPhone = _userPhone;
    this.userName = _userName;
    devinfo().then((value) {
      userAgent = value.manufacturer;
    });

    var _url = "ws://$_host:$_p2pPort$_api?token=$token&phone=$userPhone";

    _socket = RtcSocket(_url);

    /// TODO 穿透使用 没有实现
    _rtcIceServer = RtcIceServer(_host, _turnPort);
    _rtcIceServer.init();

    _socket.onOpenCallback = () {
      signalingCallback?.onSignalingCallback?.call(SignalingState.state_open);
      // 可以触发上线通知
    };

    _socket.onMessageCallback = (msg) {
      signalingCallback?.onMessageCallback?.call(msg);
      _messageArrive(_decoder.convert(msg));
    };

    _socket.onCloseCallback = (code, msg) {
      signalingCallback?.onSignalingCallback?.call(SignalingState.state_close);
    };

    _socket.onErrorCallback = (code, msg) {
      signalingCallback?.onSignalingCallback?.call(SignalingState.state_error);
    };

    await _socket.connect();
  }

  @override
  close() {
    _socket?.close();
  }

  @override
  endCall(String peerID, {bool isSelfCancel = false}) async {
    /// TODO 单项通话 删除所有 测试 确认 被呼叫方挂断的问题在哪里
    // _closeSession(_sessions[peerID]);
    _closeSession();
    if (isSelfCancel == false) {
      _send('bye', {
        'session_id': userPhone + '-' + peerID,
        'from': userPhone,
        'to': peerID,
      });
    }
  }

  @override
  muteMicroPhone() {
    if (_localStream != null) {
      bool enabled = _localStream.getAudioTracks()[0].enabled;
      _localStream.getAudioTracks()[0].enabled = !enabled;
      LogPrint.getInstance().printInfo("麦克风状态 $enabled");
    }
  }

  @override
  muteSpeaker() {}

  @override
  switchCamera() async {
    if (null != _localStream) {
      final videoTrack = _localStream
          .getVideoTracks()
          .firstWhere((track) => track.kind == 'video');
      await Helper.switchCamera(videoTrack);
    }
  }

  @override
  captureFrame() async {
    if (_localStream == null) throw Exception('Stream is not initialized');
    final videoTrack = _localStream
        .getVideoTracks()
        .firstWhere((element) => element.kind == 'video');

    ///TODO captureFrame的参数是不是保存路径
    ///  final storagePath = await getExternalStorageDirectory();
    // if (storagePath == null) throw Exception('Can\'t find storagePath');
    final frame = await videoTrack.captureFrame();
  }

  @override
  startRecordAudio() async {
    if (_localStream == null) throw Exception('Stream is not initialized');
    if (Platform.isIOS) {
      print('Recording is not available on iOS');
      return;
    }
    final storagePath = await getExternalStorageDirectory();
    if (storagePath == null) throw Exception('Can\'t find storagePath');
    final filePath = storagePath.path + '/webrtc_sample/test.mp4';
    _mediaRecorder = MediaRecorder();

    final audioTrack = _localStream
        .getVideoTracks()
        .firstWhere((track) => track.kind == 'audio');
    await _mediaRecorder.start(
      filePath,
      videoTrack: audioTrack,
    );
  }

  /// TODO 这种状态管理还是等待外部处理好了
  @override
  startRecordVideo() async {
    if (_localStream == null) throw Exception('Stream is not initialized');
    if (Platform.isIOS) {
      print('Recording is not available on iOS');
      return;
    }
    final storagePath = await getExternalStorageDirectory();
    if (storagePath == null) throw Exception('Can\'t find storagePath');
    // TODO: implement startRecordVideo
    throw UnimplementedError();
  }

  @override
  stopRecordAudio() async {
    await _mediaRecorder?.stop();
    _mediaRecorder = null;
  }

  @override
  stopRecordVideo() {
    // TODO: implement stopRecordVideo
    throw UnimplementedError();
  }

  _send(String type, data) {
    try {
      var request = Map();
      request["type"] = type;
      request["data"] = data;
      _socket.send(_encoder.convert(request));
      //Json转码后发送
      print('Json转码后发送  $type _send ${_encoder.convert(request)}');
    } catch (e) {
      LogPrint.getInstance().printError('Json转码后发送  $type err ${e.toString()}');
    }
  }

  _messageArrive(msg) async {
    print('接收到信息 $msg');

    Map<String, dynamic> mapData = msg;
    if (mapData['data'] == "") {
      return;
    }
    Map<String, dynamic> data = mapData['data'];
    switch (mapData['type']) {
      case 'peers': //多人群聊触发，当前后台未处理
        // TODO
        // 貌似是一次性返回所有数据  后期需要删减
        List<dynamic> peers = data as List;
        Map<String, dynamic> event = Map();
        event['peers'] = peers;
        signalingCallback?.onPeersUpdate?.call(event);
        break;
      case 'offer': //提议offer
        var peerID = data['from']; // 请求方唯一标识作为 管道ID？
        var description = data['description'];
        var media = data['media']; //TODO 这个是枚举类型?还是约束
        var sessionID = data['session_id'];
        var session = _sessions[sessionID]; // 获取到当前会话,此时肯定为空，所以直接继续创建
        var newSession = await _createSession(
            session: session,
            peerID: peerID,
            sessionID: sessionID,
            type: media,
            screenSharing: false);
        _sessions[sessionID] = newSession;

        await newSession.pc.setRemoteDescription(
            RTCSessionDescription(description['sdp'], description['type']));
        await _createAnswer(newSession);
        if (newSession.remoteCandidates.isNotEmpty) {
          newSession.remoteCandidates.forEach((candidate) async {
            await newSession.pc.addCandidate(candidate);
          });
          newSession.remoteCandidates.clear();
        }
        // 状态更新：
        signalingCallback?.onCallStateCallback
            ?.call(newSession, CallState.state_new);
        break;
      case 'answer': //应答Answer
        var description = data['description'];
        var sessionID = data['session_id'];
        var session = _sessions[sessionID];
        await session?.pc?.setRemoteDescription(
            RTCSessionDescription(description['sdp'], description['type']));
        break;
      case 'candidate': //网络Candidate信息
        var peerID = data['form'];
        var candidateMap = data['candidate'];
        var sessionId = data['session_id'];
        var session = _sessions[sessionId];
        RTCIceCandidate candidate = RTCIceCandidate(candidateMap['candidate'],
            candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);

        if (session != null) {
          if (session.pc != null) {
            await session.pc.addCandidate(candidate);
          } else {
            session.remoteCandidates.add(candidate);
          }
        } else {
          _sessions[sessionId] = Session(pid: peerID, sid: sessionId)
            ..remoteCandidates.add(candidate);
        }
        break;
      case 'leave': //离开房间消息  暂时未处理，后期使用需要更改
        _closeSessionByPeerID(data);
        break;
      case 'bye': //挂断信息
        var sessionID = data['session_id'];
        print('bye:  ${data.runtimeType}   内容 $data');
        var session = _sessions.remove(sessionID);
        await _closeSession(session);
        signalingCallback?.onCallStateCallback
            ?.call(session, CallState.state_bye);
        break;
      case 'request':
        var session = Session(
            pid: data["from"],
            sid: sprintf("%s-%s", [data["from"], data["to"]]),
            sessionType: data["type"]);
        signalingCallback?.onCallStateCallback
            ?.call(session, CallState.state_ring);
        break;
      case 'cancel':
        LogPrint.getInstance().printError("呼叫方自行关闭");

        /// 己方长时间无响应
        // endCall(peerID)
        break;
      case 'response': //refuse  agree
        var _session = Session(
            pid: data["to"],
            sid: sprintf("%s-%s", [data["from"], data["to"]]),
            sessionType: data["type"]);
        if (data["result"] == "agree") {
          call(data["from"], data["type"]);
          signalingCallback?.onCallStateCallback
              ?.call(_session, CallState.state_agree);
        } else {
          signalingCallback?.onCallStateCallback
              ?.call(_session, CallState.state_refuse);
          LogPrint.getInstance().printError("拒绝了通话请求");
          endCall(data["to"], isSelfCancel: true);
        }
        break;
      default:
        break;
    }
  }

  // 开始呼叫
  @override
  call(String peerID, type) async {
    //唯一管道 唯一标识 sessionID
    var sessionID = sprintf("%s-%s", [userPhone, peerID]);
    print('开始呼叫 $sessionID');
    Session session = await _createSession(
        peerID: peerID,
        sessionID: sessionID,
        type: type,
        screenSharing: type == SessionType.SCREEN ? true : false);

    _sessions[sessionID] = session;
    if (type == SessionType.DATA) {
      await _createDataChannel(session);
    }
    await _createOffer(session, type);
    signalingCallback?.onCallStateCallback
        ?.call(session, CallState.state_invite);
  }

  // 创建会话
  Future<Session> _createSession(
      {Session session,
      String peerID,
      String sessionID,
      String type,
      bool screenSharing = false}) async {
    var newSession =
        session ?? Session(sid: sessionID, pid: peerID, sessionType: type);

    if (type != SessionType.DATA) {
      _localStream = await _createStream(null, screenSharing);
    }

    RTCPeerConnection pc = await createPeerConnection(
        _rtcIceServer.iceServers, P2PConstraints.pcConstraints);

    if (type != SessionType.DATA) {
      _localStream
          .getTracks()
          .forEach((track) async => await pc.addTrack(track, _localStream));
    }

    pc.onIceCandidate = (candidate) {
      if (candidate == null) return;
      _send('candidate', {
        'to': peerID,
        'from': userPhone,
        'candidate': {
          'sdpMLineIndex': candidate.sdpMlineIndex,
          'sdpMid': candidate.sdpMid,
          'candidate': candidate.candidate,
        },
        'session_id': sessionID,
      });
    };
    // Ice连接状态
    pc.onIceConnectionState = (state) {
      // TODO
    };
    pc.onTrack = (event) {
      if (event.track.kind == 'video') {
        signalingCallback?.onAddRemoteStream
            ?.call(newSession, event.streams[0]);
      }
    };
    // pc.onAddStream
    pc.onRemoveStream = (stream) {
      _remoteStreams?.removeWhere((it) {
        signalingCallback?.onRemoveRemoteStream?.call(newSession, it);
        return (it.id == stream.id);
      });
    };

    pc.onDataChannel = (channel) {
      _addDataChannel(newSession, channel);
    };
    newSession.pc = pc;
    return newSession;
  }

  /// 创建流
  Future<MediaStream> _createStream(media, bool isUseScreen) async {
    MediaStream stream = isUseScreen
        ? await navigator.mediaDevices
            .getDisplayMedia(P2PConstraints.mediaConstraints)
        : await navigator.mediaDevices
            .getUserMedia(P2PConstraints.mediaConstraints);

    signalingCallback?.onLocalStream?.call(null, stream);
    return stream;
  }

  _addDataChannel(Session session, RTCDataChannel channel) {
    channel.onDataChannelState = (e) {};
    channel.onMessage = (data) {
      signalingCallback?.onDataChannelMessageCallback
          ?.call(session, channel, data);
    };
    session.dc = channel;
    signalingCallback?.onDataChannelCallback?.call(session, channel);
  }

  Future<void> _createDataChannel(Session session,
      {String label = 'fileTransfer'}) async {
    RTCDataChannelInit dataChannelDict = RTCDataChannelInit()
      ..maxRetransmits = 30;
    RTCDataChannel channel =
        await session.pc.createDataChannel(label, dataChannelDict);
    _addDataChannel(session, channel);
  }

  /// 创建提议Offer
  Future<void> _createOffer(Session session, String media) async {
    try {
      var constraints = media == SessionType.VIDEO
          ? P2PConstraints.sdpConstraints
          : P2PConstraints.dataSdpConstraints;

      //返回SDP信息
      RTCSessionDescription s = await session.pc.createOffer(constraints);
      //设置本地描述信息
      await session.pc.setLocalDescription(s);
      //发送Offer至对方
      _send('offer', {
        'to': session.pid,
        'from': userPhone,
        'description': {'sdp': s.sdp, 'type': s.type},
        'session_id': session.sid,
        'media': media,
      });
    } catch (e) {
      print(e.toString());
    }
  }

  /// 创建应答
  _createAnswer(Session session) async {
    try {
      RTCSessionDescription description =
          await session.pc.createAnswer(P2PConstraints.sdpConstraints);

      await session.pc.setLocalDescription(description);
      _send('answer', {
        'to': session.pid,
        'from': userPhone,
        'description': {'sdp': description.sdp, 'type': description.type},
        'session_id': session.sid,
      });
    } catch (e) {
      print('_createAnswer 失败 ${e.toString()}');
    }
  }

  _closeSessionByPeerID(peerID) {
    Session session;

    _sessions.removeWhere((key, value) {
      var ids = key.split('-');
      session = value;
      return peerID == ids[0] || peerID == ids[1];
    });
    if (session != null) {
      _closeSession(session);
      signalingCallback?.onCallStateCallback
          ?.call(session, CallState.state_bye);
    }
  }

  _closeSession([Session session]) async {
    if (session == null) {
      _sessions.forEach((key, value) {
        value?.pc?.close();
        value?.dc?.close();
        session?.remoteCandidates?.clear();
      });
    } else {
      await session?.pc?.close();
      await session?.dc?.close();
      session?.remoteCandidates?.clear();
    }

    _localStream?.getTracks()?.forEach((element) async {
      await element.stop();
    });
    await _localStream?.dispose();
    _localStream = null;
  }

  requestCall(String peerID, String type) {
    _send('request', {
      'to': peerID,
      'from': userPhone,
      "type": type,
    });
  }

  answerCall(String peerID, String type, bool result) {
    _send('response', {
      'to': peerID,
      'from': userPhone,
      "type": type,
      'result': result ? "agree" : "refuse",
    });
  }
}
